/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#ifndef SCI_GRAPHICS_SCREEN_ITEM32_H
#define SCI_GRAPHICS_SCREEN_ITEM32_H

#include "common/rect.h"
#include "sci/graphics/celobj32.h"
#include "sci/graphics/lists32.h"

namespace Sci {

enum ScaleSignals32 {
	kScaleSignalNone           = 0,
	kScaleSignalManual         = 1,
	kScaleSignalVanishingPoint = 2
};

struct ScaleInfo {
	int x, y, max;
	ScaleSignals32 signal;
	ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {}
};

class CelObj;
class Plane;
class SegManager;

#pragma mark -
#pragma mark ScreenItem

/**
 * A ScreenItem is the engine-side representation of a game script View.
 */
class ScreenItem {
private:
	/**
	 * A serial used for screen items that are generated inside the graphics
	 * engine, rather than the interpreter.
	 */
	static uint16 _nextObjectId;

	/**
	 * A serial used to identify the creation order of screen items, to ensure a
	 * stable sort order for screen items with identical priorities and
	 * z-indexes.
	 */
	static uint32 _nextCreationId;

public:
	/**
	 * The parent plane of this screen item.
	 */
	reg_t _plane;

	/**
	 * Scaling data used to calculate the final screen dimensions of the screen
	 * item as well as the scaling ratios used when drawing the item to screen.
	 */
	ScaleInfo _scale;

private:
	/**
	 * The position & dimensions of the screen item in screen coordinates. This
	 * rect includes the offset of the parent plane, but is not clipped to the
	 * screen, so may include coordinates that are offscreen.
	 */
	Common::Rect _screenItemRect;

	/**
	 * If true, the `_insetRect` rectangle will be used when calculating the
	 * dimensions of the screen item instead of the cel's intrinsic width and
	 * height.
	 *
	 * In other words, using an inset rect means that the cel is cropped to the
	 * dimensions given in `_insetRect`.
	 */
	bool _useInsetRect;

	/**
	 * The cropping rectangle used when `_useInsetRect` is true.
	 *
	 * `_insetRect` is also used to describe the fill rectangle of a screen item
	 * with a CelObjColor cel.
	 */
	Common::Rect _insetRect;

	/**
	 * The z-index of the screen item in pseudo-3D space. Higher values are
	 * drawn on top of lower values.
	 */
	int _z;

	/**
	 * Sets the common properties of a screen item that must be set both during
	 * creation and update of a screen item.
	 */
	void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap);

public:
	/**
	 * The creation order number, which ensures a stable sort when screen items
	 * with identical priorities and z-indexes are added to the screen item
	 * list.
	 */
	uint32 _creationId;

	/**
	 * A descriptor for the cel object represented by the screen item.
	 */
	CelInfo32 _celInfo;

	/**
	 * The cel object used to actually render the screen item. This member is
	 * populated by calling `getCelObj`.
	 */
	mutable Common::ScopedPtr<CelObj> _celObj;

	/**
	 * If set, the priority for this screen item is fixed in place. Otherwise,
	 * the priority of the screen item is calculated from its y-position +
	 * z-index.
	 */
	bool _fixedPriority;

	/**
	 * The rendering priority of the screen item, relative only to the other
	 * screen items within the same plane. Higher priorities are drawn above
	 * lower priorities.
	 */
	int16 _priority;

	/**
	 * The top-left corner of the screen item, in game script coordinates,
	 * relative to the parent plane.
	 */
	Common::Point _position;

	/**
	 * The associated View script object that was used to create the ScreenItem,
	 * or a numeric value in the case of a ScreenItem that was generated by the
	 * kernel.
	 */
	reg_t _object;

	/**
	 * For screen items representing picture resources, the resource ID of the
	 * picture.
	 */
	GuiResourceId _pictureId;

	/**
	 * Flags indicating the state of the screen item.
	 * - `created` is set when the screen item is first created, either from a
	 *   VM object or from within the kernel
	 * - `updated` is set when `created` is not already set and the screen item
	 *   is updated from a VM object
	 * - `deleted` is set by the parent plane, if the parent plane is a pic type
	 *   and its picture resource ID has changed
	 */
	int _created, _updated, _deleted;

	/**
	 * For screen items that represent picture cels, this value is set to match
	 * the `_mirrorX` property of the parent plane and indicates that the cel
	 * should be
	 * drawn horizontally mirrored. For final drawing, it is XORed with the
	 * `_mirrorX` property of the cel object. The cel object's `_mirrorX`
	 * property comes from the resource data.
	 */
	bool _mirrorX;

	/**
	 * The scaling ratios to use when drawing this screen item. These values are
	 * calculated according to the scale info whenever the screen item is
	 * updated.
	 */
	Ratio _ratioX, _ratioY;

	/**
	 * The top-left corner of the screen item, in screen coordinates.
	 */
	Common::Point _scaledPosition;

	/**
	 * The position & dimensions of the screen item in screen coordinates. This
	 * rect includes the offset of the parent plane and is clipped to the
	 * screen.
	 */
	Common::Rect _screenRect;

	/**
	 * Whether or not the screen item should be drawn with black lines drawn
	 * every second line. This is used when pixel doubling videos to improve
	 * apparent sharpness at the cost of your eyesight.
	 */
	bool _drawBlackLines;

	/**
	 * Initialises static Plane members.
	 */
	static void init();

	ScreenItem(const reg_t screenItem);
	ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
	ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
	ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo);
	ScreenItem(const ScreenItem &other);
	void operator=(const ScreenItem &);

	inline bool operator<(const ScreenItem &other) const {
		if (_priority < other._priority) {
			return true;
		}

		if (_priority == other._priority) {
			if (_position.y + _z < other._position.y + other._z) {
				return true;
			}

			if (_position.y + _z == other._position.y + other._z) {
				// Synthetic object IDs (numeric IDs) are used for screen items
				// generated by the kernel, like the screen items generated by
				// plane pics. In SSCI, these synthetic IDs started at 20000 so
				// would deterministically always sort higher than any
				// script-generated view in SSCI at the same position and
				// priority.
				if (other._object.isNumber() && !_object.isNumber()) {
					return true;
				}

				// SSCI's last resort comparison here is to compare the _object
				// IDs, but this is wrong and randomly breaks (at least):
				//
				// (1) the death dialog at the end of Phant1, where the ID of
				//     the text is often higher than the ID of the border;
				// (2) text-based buttons and dialogues in Hoyle5, where the ID
				//     of the text is often lower than the ID of the
				//     button/dialogue background.
				//
				// This occurs because object IDs (in both ScummVM and SSCI) are
				// reused, so objects created later may receive a lower ID,
				// which makes them sort lower when the programmer intended them
				// to sort higher.
				//
				// To fix this problem, we give each ScreenItem a monotonically
				// increasing insertion ID at construction time, and compare
				// these insertion IDs instead. They are more stable and cause
				// objects with identical priority and z-index to be rendered in
				// the order that they were created.
				//
				// This also applies to ScreenItem::hasPriorityAbove.
				return _creationId < other._creationId;
			}
		}

		return false;
	}

	inline bool operator>(const ScreenItem &other) const {
		if (_priority > other._priority) {
			return true;
		}

		if (_priority == other._priority) {
			if (_position.y + _z > other._position.y + other._z) {
				return true;
			}

			if (_position.y + _z == other._position.y + other._z) {
				if (_object.isNumber() && !other._object.isNumber()) {
					return true;
				}

				// This is different than SSCI; see ScreenItem::operator< for an
				// explanation
				return _creationId > other._creationId;
			}
		}

		return false;
	}

	inline bool hasPriorityAbove(const ScreenItem &other) const {
		if (_priority > other._priority) {
			return true;
		}

		if (_priority == other._priority) {
			if (_object.isNumber() && !other._object.isNumber()) {
				return true;
			}

			// This is different than SSCI; see ScreenItem::operator< for an
			// explanation
			return _creationId > other._creationId;
		}

		return false;
	}

	/**
	 * Calculates the dimensions and scaling parameters for the screen item,
	 * using the given plane as the parent plane for screen rect positioning.
	 *
	 * @note This method was called Update in SSCI.
	 */
	void calcRects(const Plane &plane);

	/**
	 * Retrieves the corresponding cel object for this screen item. If a cel
	 * object does not already exist, one will be created and assigned.
	 */
	CelObj &getCelObj() const;

	void printDebugInfo(Console *con) const;

	/**
	 * Updates the properties of the screen item from a VM object.
	 */
	void update(const reg_t object);

	/**
	 * Updates the properties of the screen item for one not belonging to a VM
	 * object. Originally GraphicsMgr::UpdateScreenItem.
	 */
	void update();

	/**
	 * Gets the "now seen" rect for the screen item, which represents the
	 * current size and position of the screen item on the screen in script
	 * coordinates.
	 */
	Common::Rect getNowSeenRect(const Plane &plane) const;
};

#pragma mark -
#pragma mark ScreenItemList

typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
class ScreenItemList : public ScreenItemListBase {
private:
	size_type _unsorted[250];

public:
	ScreenItem *findByObject(const reg_t object) const;
	void sort();
	void unsort();
};
} // End of namespace Sci

#endif
